<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>迭代器模式</title>
</head>
<body>
    <p>迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素 而又不需要暴露该对象的内部表示</p>
    <p>迭代器模式可以把迭代的过程从业务逻辑中分离出来 在使用迭代器模式之后 即使不关心对象的内部构造 也可以按顺序的访问其中的每个元素</p>

</body>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>

    // 1、实现自己的迭代器
    var each = function(ary, callback) {
        for(var i = 0, l = ary.length; i < l; i++) {
            let v = callback.call(ary[i], i, ary[i])
            // 这里可以终止迭代器执行
            if ( v === false) break;

        }
    }
    each([1, 3, 4], function(i, n) {
        console.log(i, 'n', n);
    })

    // 2、内部迭代器与外部迭代器

    // 2.1 内部迭代器
    // 我们刚刚编写的 each就属于内部迭代器 each 函数的内部已经定义好了迭代规则 他完全接手整个迭代过程 外部只需要一次初始调用
    // 内部迭代器在调用的时候十分方便 外界不需要关心迭代器的内部实现 跟迭代器的交互也仅是一次初始化调用 但这也刚好是内部迭代器的缺点 
    // 由于内部迭代器的迭代规则已经被提前规定 上面的each函数就无法同时迭代2个数组了

    // 比如现在有个需求 要判断2个数组里元素的值是否完全相等 如果不改写 each函数本身的代码 我们能够入手的地方 似乎只剩下each 的回调函数了 代码如下

    var compare = function( ary1, ary2 ) {
        if ( ary1.length !== ary2.length ) {
            throw new Error('ary1和2不相等')
        }
        each(ary1, function(i, item){
            if (n !== ary2[i]) {
                throw new Error('不相等')
            }
        })
        console.log('相等');
    }

    // 2.2 外部迭代器
    // 外部迭代器必须显式的请求迭代下一个元素
    // 外部迭代器增加了一些调用的复杂度 但相对也加强了迭代器的灵活性 我们可以手工控制迭代的过程或者顺序

    var Iterator = function(obj) {
        var current = 0;

        var next = function() {
            current += 1
        }
        var isDone = function() {
            return current >= obj.length
        }
        
        var getCurrentItem = function() {
            return obj[current]
        }

        return {
            next,
            isDone,
            getCurrentItem
        }
    }
    let compare2 = function(iterator1, iterator2) {
        while (!iterator1.isDone() && !iterator2.isDone() ) {
            if (iterator1.getCurrentItem() !== iterator2.getCurrentItem() ) {
                // throw new Error('不相等')
               return console.log('不相等');
            }
            iterator1.next()
            iterator2.next()
        }
        // alert('相等')
        console.log('相等');
    }
    let i1 = Iterator([1,2,3])
    let i2 = Iterator([2,1,3])
    compare2(i1, i2)


    // 3、倒序迭代器

    var reverseEach = function(ary, callback) {
        for (var l = ary.length - 1; l >= 0; l--) {
            callback(l, ary[i])
        }
    }


    // 4、终止迭代器
    // 迭代器可以像普通for循环中的break一样跳出循环 jQuery 中的 each 函数里有这样一句 
    // if ( value === false ) {
    //     break;
    // }

        
    // 应用

    // 某个项目上传文件场景,有如下一段代码 他是根据不同的浏览器来获取相应的上传组件对象
    let supportFlash = function() {
        return false
    }
    var getUploadObj = function() {
        try {
            return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传
        } catch(e) {
            if ( supportFlash() ) {     // supportFlash 函数未提供
                var str = '<object type="application/x-shockwave-flash"></object>'
                return $(str).appendTo($('body'));
            } else {
                var str = '<input name="file" type="file" />';   // 表单上传
                return $(str).appendTo($('body'));
            }
        }
    }
    // 在不同的浏览器情况下 选择上传的方式是不一样的 因为使用浏览器的上传控件进行上传的速度很快 可以暂停和续传
    // 所以我们会优先选择控件上传 
    // 上面代码的缺点:
    // 1、难阅读，充满了 try catch if else 
    // 2、违反开闭原则 在开发和调试过程中 我们需要来回切换不同的上传方式 
    // 3、每次改动相当痛苦 如果后面还增加支持HTML5 上传 唯一办法就是修改 getUploadObj


    // 使用迭代器模式进行修改
    // 我们把每种获取upload对象的方法都封装在各自的函数里 然后使用一个迭代器 迭代获取这些upload对象 直到获取一个可用的为止

    let getActiveUploadObj = function() {
        try {
            return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传
        } catch(e) {
            return false
        }
    }
    let getFlashUploadObj = function() {
        if (supportFlash()) {
            let str = '<object type="application/x-shockwave-flash"></object>'
            return $(str).appendTo($('body'));
        }
        return false
    }

    let getFormUploadObj = function() {
        let str = '<input name="file" type="file" class="ui-file" />';
        return $(str).appendTo($('body'))
    }

    // 提供一个可以被迭代的方法 
    // 如果正在被迭代的函数返回一个对象 则表示找到了正确的upload方法 反之返回false 则让迭代器继续工作
    var iteratorUploadObj = function() {
        for (let i = 0, fn; fn = arguments[i ++ ];) {
            var uploadObj = fn();
            if (uploadObj !== false) {
                return uploadObj;
            }
        }
    }

    let uploadObj = iteratorUploadObj(getActiveUploadObj, getFlashUploadObj, getFormUploadObj)


</script>
</html>